﻿using SdmxMl.Common;
using SdmxMl.Helper;
using SdmxMl.Multiplexor;
using SdmxMl.Structure;
using System;
using System.Collections.Generic;
using System.IO;
using System.Xml;

namespace SdmxMl.Manager
{
    #region Enumerate Artefacts

    public enum SdmxArtefactType
    {
        None,
        OrganisationSchemes,
        Dataflows,
        Metadataflows,
        CategorySchemes,
        Categorisations,
        CodeLists,
        HierarchicalCodelists,
        Concepts,
        MetadataStructureDefinitions,
        KeyFamilies,
        StructureSets,
        ReportingTaxonomies,
        Processes,
        DataSet,
        MetadataSet,
    }

    #endregion //Enumerate Artefacts

    #region Enumerate Web Tree groups

    public enum EWebTreeGroup
    {
        None,
        HierarchyLevel,
        DsdDimension,
        DsdMeasure,
        DsdAttribute,
        DsdGroup,
        MsdReport,
        MsdTargetId,
        MsdTargetPartialId,
        OrgAgency,
        OrgDataProvider,
        OrgDataConsumer,

    }

    #endregion //Enumerate Artefacts

    #region Enumerate Artefact detail

    public enum ArtefactDetail
    {
        Full,
        AllStubs,
        ReferenceStubs
    }
    #endregion

    /// <summary>
    /// Manage Structure message Artefacts documents
    /// </summary>
    public class SmManager
    {
        #region Internal Members

        private SdmxNamespaceManager _nsmgr;

        private ArtefactManagerDico _artefactManagerDico;

        #endregion

        #region Properties

        public ArtefactManagerDico ArtefactManagerDico
        {
            get { return _artefactManagerDico; }
        }
        #endregion

        #region Construction

        public SmManager()
        {
            _artefactManagerDico = new ArtefactManagerDico();
            PrepareArtefacts();
        }

        /// <summary>
        /// Creates a manager for each type of supported artefact
        /// </summary>
        private void PrepareArtefacts()
        {
            foreach (SdmxArtefactType atype in Enum.GetValues(typeof(SdmxArtefactType)))
            {
                if (atype != SdmxArtefactType.None)
                {
                    if (atype == SdmxArtefactType.DataSet)
                        _artefactManagerDico.Add(atype, new DataSetManager(atype));

                    else if (atype == SdmxArtefactType.MetadataSet)
                        _artefactManagerDico.Add(atype, new MetadataSetManager(atype));
                    else
                        _artefactManagerDico.Add(atype, new ArtefactManager(atype));
                }
            }
        }

        public void Clear()
        {
            foreach (ArtefactManager am in _artefactManagerDico.Values)
                am.Clear();
        }

        #endregion

        #region Load Artefac(s) from SDMX Document

        /// <summary>Load Artefact(s) from SDMX file Document</summary>
        /// <param name="file">file path</param>
        /// <param name="append">If true add to existing</param>
        /// <param name="xDoc">if Not null, use document instead of file</param>
        public void LoadSdmxDocument(string file, bool append, XmlDocument xDoc)
        {
            // Clear actual artefacts before loading
            if (append == false)
                Clear();

            // Read document if needed 
            if (xDoc == null && file != null && System.IO.Path.GetExtension(file).ToLower() == ".xml")
            {
                xDoc = new XmlDocument();
                xDoc.Load(file);
            }
            if (xDoc != null)
            {

                //Create an XmlNamespaceManager for resolving namespaces.
                _nsmgr = Xmlhelper.CreateSdmxNamespace(xDoc);

                foreach (ArtefactManager mgr in _artefactManagerDico.Values)
                {
                    int previousCount = mgr.Count;
                    mgr.LoadFromDocument(xDoc, _nsmgr);
                }
            }
        }


        public void LoadMissingArtefactsFromReferenceMgr(ArtefactRefTypedList artl)
        {
            UpdateLoadedStatus(artl);

            foreach (ArtefactRefTyped art in artl)
            {
                if (art.IsLoaded == false)
                {
                    Artefact a = ReferenceManager.LocateArtefact(art.Type, art);
                    if (a != null)
                    {
                        ArtefactManagerDico[art.Type].ArtefactList.Add(a);
                        art.IsLoaded = true;
                    }
                }
            }
        }

        public void LoadMissingArtefactsFromRegistry(ArtefactRefTypedList artl)
        {
            LoadMissingArtefactsFromRegistry(artl, ArtefactDetail.Full);
        }

        public void LoadMissingArtefactsFromRegistry(ArtefactRefTyped art)
        {
            ArtefactRefTypedList list = new ArtefactRefTypedList();
            list.Add(art);
            LoadMissingArtefactsFromRegistry(list, ArtefactDetail.Full);
        }


        public void LoadMissingArtefactsFromRegistry(ArtefactRefTypedList artl, ArtefactDetail detail)
        {
            UpdateLoadedStatus(artl);
            foreach (ArtefactRefTyped art in artl)
            {
                if (art.IsLoaded == false && art.IsFilled)
                {
                    if (detail == ArtefactDetail.Full)
                    {
                        XmlDocument xDoc = Mux.Instance.GetArtefact(art.Type.ToString(), art.FullIdent);
                        if (xDoc != null)
                        {

                            //Create an XmlNamespaceManager for resolving namespaces.
                            _nsmgr = Xmlhelper.CreateSdmxNamespace(xDoc);

                            // Returned document can be aggregated, so iterate for all
                            foreach (ArtefactManager mgr in _artefactManagerDico.Values)
                                mgr.LoadFromDocument(xDoc, _nsmgr);
                        }
                    }
                    else
                    {
                        ArtefactManager mgr = ArtefactManagerDico[art.Type];
                        Artefact a = mgr.CreateArtefact();
                        a.Id = art.Id;
                        a.AgencyId = art.AgencyId;
                        a.Version = art.Version;
                        a.Name = art.Name;
                        a.IsFinal = art.IsFinal;
                        a.IsExternalReference = true;
                        mgr.ArtefactList.Add(a);
                    }
                }
            }
            UpdateLoadedStatus(artl);
        }
        #endregion //Load Artefact(s)


        /// <summary>Add artefact in this cacke </summary>
        /// <param name="arType">Type of atefact</param>
        /// <param name="a">Specified artefact instance</param>
        public void PutArtefact(SdmxArtefactType arType, Artefact a)
        {
            // Add if exists and not yet in cache
            if (a != null && arType != SdmxArtefactType.None)
            {
                if (ArtefactManagerDico[arType].Exists(a) == false)
                    ArtefactManagerDico[arType].ArtefactList.Add(a);
            }
        }

        /// <summary>Remove specified artefact of this cache</summary>
        /// <param name="art">artefact specification</param>
        public void RemoveArtefact(ArtefactRefTyped art)
        {
            // Add if exists and not yet in cache
            if (art != null && art.Type != SdmxArtefactType.None)
            {
                Artefact a = ArtefactManagerDico[art.Type].GetFromFullId(art.FullIdent);
                if (a != null)
                    ArtefactManagerDico[art.Type].ArtefactList.Remove(a);
            }
        }

        public void UpdateLoadedStatus(ArtefactRefTypedList artl)
        {
            if (artl != null)
            {
                foreach (ArtefactRefTyped art in artl)
                {
                    art.IsLoaded = false;
                    GetArtefact(art);
                }
            }
        }
        /// <summary>
        /// Get already memorized Artefact if exists in container. Update IsLoaded status of art.
        /// </summary>
        /// <param name="art">Typed artefact reference object</param>
        /// <returns>null if not in container</returns>
        public Artefact GetArtefact(ArtefactRefTyped art)
        {
            Artefact a = null;
            if (art != null)
            {
                a = GetArtefact(art.Type, art);
                art.IsLoaded = a != null;
            }
            return a;
        }
        /// <summary>
        /// Get specified type of refered artefact if already in memory
        /// </summary>
        /// <param name="arType">Requested type of artefact</param>
        /// <param name="aRef">Artefact reference interface object</param>
        /// <returns>null if not in container</returns>
        public Artefact GetArtefact(SdmxArtefactType arType, ISdmxArtefactRef aRef)
        {
            return GetArtefact(arType, aRef, false);
        }

        public XmlDocument GetArtefactAsDocument(SdmxArtefactType arType, ISdmxArtefactRef aRef)
        {
            Artefact a = GetArtefact(arType, aRef, false);
            SmManager sm = new SmManager();
            sm.PutArtefact(a.ArType, a);
            if (arType == SdmxArtefactType.MetadataSet)
                return sm.GetMetasetAsDocument(aRef.FullIdent);
            else
                return sm.GetAsDocument();
        }

        /// <summary>
        /// Get specified type of refered artefact if already in memory of fetch from registry
        /// </summary>
        /// <param name="arType">Requested type of artefact</param>
        /// <param name="aRef">Artefact reference interface object</param>
        /// <param name="useRegistry">if True, fetcvh from registry if not in memory</param>
        /// <returns>null if not in container</returns>
        public Artefact GetArtefact(SdmxArtefactType arType, ISdmxArtefactRef aRef, bool useRegistry)
        {
            Artefact ar = null;
            bool isFilledRef = aRef != null && string.IsNullOrEmpty(aRef.Id) == false && string.IsNullOrEmpty(aRef.AgencyId) == false;
            // TODO metaset
            ArtefactList arList = GetArtefactList(arType);
            if (arList != null && isFilledRef)
            {
                foreach (Artefact a in arList)
                   // if (a.FullIdent.ToUpper() == aRef.FullIdent.ToUpper())
                    if (a.FullIdent == aRef.FullIdent)
                        {
                        ar = a;
                        break;
                    }
            }
            if (isFilledRef && ar == null && useRegistry == true)
            {
                ArtefactRefTypedList artefactRefList = new ArtefactRefTypedList();
                ArtefactRefTyped at = new ArtefactRefTyped(arType, aRef.Id, aRef.AgencyId, aRef.Version);
                artefactRefList.Add(at);
                LoadMissingArtefactsFromRegistry(artefactRefList);
                ar = GetArtefact(at);
            }
            return ar;
        }

        public ArtefactList GetArtefactList(SdmxArtefactType arType)
        {
            ArtefactList arList = null;
            if (_artefactManagerDico.ContainsKey(arType))
                arList = _artefactManagerDico[arType].ArtefactList;
            return arList;
        }

        public ArtefactManager GetArtefactManager(SdmxArtefactType arType)
        {
            ArtefactManager am = null;
            if (_artefactManagerDico.ContainsKey(arType))
                am = _artefactManagerDico[arType];
            return am;
        }

        public object GetIdentifiedObjectOfArtefact(SdmxArtefactType arType, Artefact a, string objId)
        {
            object ob = null;
            switch (arType)
            {
                case SdmxArtefactType.CategorySchemes :
                    CategorySchemeType cst = a as CategorySchemeType;
                    if (cst != null)
                        ob = cst.CategoryList.LocateItemFromFullId(objId, cst.CategoryList);
                break;

                case SdmxArtefactType.CodeLists:
                    CodeListType clt = a as CodeListType;
                    if (clt != null)
                        ob = clt.CodeList.LocateItem(objId);
                break;

                case SdmxArtefactType.Concepts:
                    ConceptSchemeType cost = a as ConceptSchemeType;
                    if (cost != null)
                        ob = cost.ConceptList.LocateItem(objId);
                break;

                case SdmxArtefactType.HierarchicalCodelists:
                    HierarchicalCodelistType hcl = a as HierarchicalCodelistType;
                    if (hcl != null && hcl.HierarchyList.Count > 0)
                        ob = hcl.HierarchyList[0].CodeRefList.LocateItemFromFullId(objId,
                            hcl.HierarchyList[0].CodeRefList);
                break;
            }

            return ob;
        }

        /// <summary>
        /// Get whole content as an XML Message Structure document
        /// </summary>
        /// <returns>the aggregated document</returns>
        public XmlDocument GetAsDocument()
        {
            return GetAsDocument(string.Empty);
        }
      
        /// <summary> Get all Structure content as a single SDMX document </summary>
        /// <param name="fullId">Id used to transmit Group of maintainable owner</param>
        /// <returns>SDMX structure document</returns>
        public XmlDocument GetAsDocument(string fullId)
        {
            XmlDocument xDoc = new XmlDocument();

            using (MemoryStream m = new MemoryStream(1024))
            {
                using (XmlTextWriter xw = new XmlTextWriter(m, System.Text.Encoding.UTF8))
                {
                    xw.Formatting = Formatting.Indented;
                    xw.WriteStartDocument();

                    SdmxMl.Msg.HeaderGenerator.GetHeader(fullId).WriteXml(xw, SdmxMl.Msg.HeaderType.ReferenceScheme.Structure);

                    foreach (SdmxArtefactType atype in Enum.GetValues(typeof(SdmxArtefactType)))
                    {
                        if (atype != SdmxArtefactType.None && atype != SdmxArtefactType.MetadataSet && atype != SdmxArtefactType.DataSet)
                        {
                            ArtefactManager am = _artefactManagerDico[atype];
                            am.WriteXml(xw, null);
                        }
                    }

                    xw.WriteEndDocument();
                    xw.Flush();
                    m.Flush();
                    m.Seek(0, SeekOrigin.Begin);
                    xDoc.Load(m);
                }
            }

            return xDoc;
        }

        public XmlDocument GetMetasetAsDocument(string fullId)
        {
            XmlDocument xDoc = new XmlDocument();

            using (MemoryStream m = new MemoryStream(1024))
            {
                using (XmlTextWriter xw = new XmlTextWriter(m, System.Text.Encoding.UTF8))
                {
                    xw.Formatting = Formatting.Indented;
                    xw.WriteStartDocument();

                    SdmxMl.Msg.HeaderGenerator.GetHeader(fullId).WriteXml(xw, SdmxMl.Msg.HeaderType.ReferenceScheme.MetadataSet);

                    ArtefactManager am = _artefactManagerDico[SdmxArtefactType.MetadataSet];
                    am.WriteXml(xw, null);

                    xw.WriteEndDocument();
                    xw.Flush();
                    m.Flush();
                    m.Seek(0, SeekOrigin.Begin);
                    xDoc.Load(m);
                }
            }

            return xDoc;
        }
        public void RemoveArtefactsAlreadyInRepository()
        {
            List<Artefact> listArtefact = new List<Artefact>();
            foreach (SdmxArtefactType atype in Enum.GetValues(typeof(SdmxArtefactType)))
            {
                if (atype != SdmxArtefactType.None && atype != SdmxArtefactType.MetadataSet && atype != SdmxArtefactType.DataSet)
                {
                    ArtefactManager am = _artefactManagerDico[atype];
                    listArtefact.Clear();
                    foreach (Artefact a in am.ArtefactList)
                    {
                        if (Mux.Instance.ExistArtefact(am.ArtefactType.ToString(), a.FullIdent))
                            listArtefact.Add(a);
                    }
                    foreach (Artefact a in listArtefact)
                        am.ArtefactList.Remove(a);
                }
            }
        }

        public void RemoveArtefactRefAlreadyInRepository(ArtefactRefTypedList refList)
        {
            List<ArtefactRefTyped> listArtefactRef = new List<ArtefactRefTyped>();
            foreach (ArtefactRefTyped art in refList)
            {
                if (Mux.Instance.ExistArtefact(art.Type.ToString(), art.FullIdent))
                    listArtefactRef.Add(art);
            }

            foreach (ArtefactRefTyped artIn in listArtefactRef)
                refList.Remove(artIn);
        }

        public Artefact CloneArtefact(Artefact a)
        {
            Artefact aClone = null;
            if (a != null)
            {
                ArtefactManager am = new ArtefactManager(a.ArType);
                am.ArtefactList.Add(a);
                XmlDocument xDoc = am.GetAsDocument(a);
                SmManager sm = new SmManager();
                sm.LoadSdmxDocument(null, false, xDoc);

                aClone = sm.GetArtefact(a.ArType, a);
            }

            return aClone;
        }

        #region Persist in registry

        public void PersistInRegistry(Artefact a)
        {
            bool exist = Mux.Instance.ExistArtefact(a.ArType.ToString(), a.FullIdent);
            if (exist)
                Mux.Instance.CheckOut(a.ArType.ToString(), a.FullIdent);
            SmManager sm = new SmManager();
            sm.PutArtefact(a.ArType, a);
            XmlDocument xDoc = (a.ArType == SdmxArtefactType.MetadataSet) ? sm.GetMetasetAsDocument(a.FullIdent) : sm.GetAsDocument();

            Mux.Instance.SubmitArtefact(a.ArType.ToString(), a.FullIdent, xDoc);
        }

        #endregion
    }

}
